/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.solr.response;

import java.io.BufferedReader;
import java.io.CharArrayReader;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.solr.core.SolrConfig;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.XMLErrorLogger;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.util.xslt.TransformerProvider;

/** QueryResponseWriter which captures the output of the XMLWriter
 *  (in memory for now, not optimal performancewise), and applies an XSLT transform
 *  to it.
 */
public class XSLTResponseWriter implements QueryResponseWriter {

  public static final String DEFAULT_CONTENT_TYPE = "application/xml";
  public static final String TRANSFORM_PARAM = "tr";
  public static final String CONTEXT_TRANSFORMER_KEY = "xsltwriter.transformer";
  
  private Integer xsltCacheLifetimeSeconds = null; 
  public static final int XSLT_CACHE_DEFAULT = 60;
  private static final String XSLT_CACHE_PARAM = "xsltCacheLifetimeSeconds"; 

  private static final Logger log = LoggerFactory.getLogger(XSLTResponseWriter.class);
  private static final XMLErrorLogger xmllog = new XMLErrorLogger(log);
  
  public void init(NamedList n) {
      final SolrParams p = SolrParams.toSolrParams(n);
      xsltCacheLifetimeSeconds = p.getInt(XSLT_CACHE_PARAM,XSLT_CACHE_DEFAULT);
      log.info("xsltCacheLifetimeSeconds=" + xsltCacheLifetimeSeconds);
  }

  
  public String getContentType(SolrQueryRequest request, SolrQueryResponse response) {
    Transformer t = null;
    try {
      t = getTransformer(request);
    } catch(Exception e) {
      // TODO should our parent interface throw (IO)Exception?
      throw new RuntimeException("getTransformer fails in getContentType",e);
    }
    
    String mediaType = t.getOutputProperty("media-type");
    if (mediaType == null || mediaType.length()==0) {
      // This did not happen in my tests, mediaTypeFromXslt is set to "text/xml"
      // if the XSLT transform does not contain an xsl:output element. Not sure
      // if this is standard behavior or if it's just my JVM/libraries
      mediaType = DEFAULT_CONTENT_TYPE;
    }
    
    if (!mediaType.contains("charset")) {
      String encoding = t.getOutputProperty("encoding");
      if (encoding == null || encoding.length()==0) {
        encoding = "UTF-8";
      }
      mediaType = mediaType + "; charset=" + encoding;
    }
    
    return mediaType;
  }

  public void write(Writer writer, SolrQueryRequest request, SolrQueryResponse response) throws IOException {
    final Transformer t = getTransformer(request);
    
    // capture the output of the XMLWriter
    final CharArrayWriter w = new CharArrayWriter();
    XMLWriter.writeResponse(w,request,response);
    
    // and write transformed result to our writer
    final Reader r = new BufferedReader(new CharArrayReader(w.toCharArray()));
    final StreamSource source = new StreamSource(r);
    final StreamResult result = new StreamResult(writer);
    try {
      t.transform(source, result);
    } catch(TransformerException te) {
      final IOException ioe = new IOException("XSLT transformation error");
      ioe.initCause(te);
      throw ioe;
    }
  }
  
  /** Get Transformer from request context, or from TransformerProvider.
   *  This allows either getContentType(...) or write(...) to instantiate the Transformer,
   *  depending on which one is called first, then the other one reuses the same Transformer
   */
  protected Transformer getTransformer(SolrQueryRequest request) throws IOException {
    final String xslt = request.getParams().get(TRANSFORM_PARAM,null);
    if(xslt==null) {
      throw new IOException("'" + TRANSFORM_PARAM + "' request parameter is required to use the XSLTResponseWriter");
    }
    // not the cleanest way to achieve this
    SolrConfig solrConfig = request.getCore().getSolrConfig();
    // no need to synchronize access to context, right? 
    // Nothing else happens with it at the same time
    final Map<Object,Object> ctx = request.getContext();
    Transformer result = (Transformer)ctx.get(CONTEXT_TRANSFORMER_KEY);
    if(result==null) {
      result = TransformerProvider.instance.getTransformer(solrConfig, xslt,xsltCacheLifetimeSeconds.intValue());
      result.setErrorListener(xmllog);
      ctx.put(CONTEXT_TRANSFORMER_KEY,result);
    }
    return result;
  }
}
